/*
 * Copyright (c) Huawei Technologies Co., Ltd. 2025. All rights reserved.
 * Description: Hmfs-tools resize load check point
 */

#include "load_check_point.h"

#include <clocale>
#include <cstdbool>
#include <cstring>
#include <ctime>
#include "resize_load.h"
#include "device_manager.h"
#include "hmfs_common.h"
#include "node.h"
#include "securec.h"

namespace OHOS {
namespace Hmfs {
LoadCheckPoint::LoadCheckPoint(ResizeLoad *loadPtr) : load_(loadPtr)
{
    config_ = load_->GetConfig();
    superBlockInfo_ = load_->GetSuperBlockInfo();
}

int32_t LoadCheckPoint::VerifyCheckPointChecksum(CheckPointData *checkPointData)
{
    HMFS_CHECK_ONLY_EXPER(checkPointData != nullptr, return EXIT_ERR_CODE);
    uint32_t chksumOffset = GetLeValue(checkPointData->checksumOffset);
    if (chksumOffset < CP_MIN_CHECKSUM_OFFSET || chksumOffset > CP_CHECKSUM_OFFSET) {
        HMFS_INFO("\tInvalid CP CRC offset: %u", chksumOffset);
        return EXIT_ERR_CODE;
    }

    auto crcAddr = reinterpret_cast<uint32_t *>(reinterpret_cast<uint8_t *>(checkPointData) + chksumOffset);
    uint32_t crc = LE32ToNative(*crcAddr);
    uint32_t calCrc = HmfsCommon::GetInstance().GetCheckpointChksum(checkPointData);
    if (calCrc != crc) {
        HMFS_INFO("\tInvalid CP CRC: offset:%u, crc:0x%x, calc:0x%x", chksumOffset, crc, calCrc);
        return EXIT_ERR_CODE;
    }
    return SUCCESS_CODE;
}

std::unique_ptr<uint8_t[]> LoadCheckPoint::GetCheckpoint(uint32_t checkPointBlockId)
{
    auto buf = std::make_unique<uint8_t[]>(HMFS_BLOCK_SIZE);
    if (HmfsIo::GetInstance().DevReadBlock(buf.get(), checkPointBlockId) < 0) {
        HMFS_ASSERT(false);
    }
    struct CheckPointData *checkPoint = reinterpret_cast<struct CheckPointData *>(buf.get());
    if (VerifyCheckPointChecksum(checkPoint) == -1) {
        PrintCheckpointInfo(checkPoint, config_->debugLevel, config_->layout);
        return nullptr;
    }
    return buf;
}

std::unique_ptr<uint8_t[]> LoadCheckPoint::ValidateCheckpoint(uint32_t checkPointBlockId, unsigned long long *version)
{
    HMFS_CHECK_ONLY_EXPER(version != nullptr, return nullptr);
    auto checkPoint1 = GetCheckpoint(checkPointBlockId);
    if (!checkPoint1) {
        return nullptr;
    }
    struct CheckPointData *checkPointData = reinterpret_cast<struct CheckPointData *>(checkPoint1.get());
    if (GetLeValue(checkPointData->checkPointPackBlockCount) > superBlockInfo_->blocksPerSeg) {
        return nullptr;
    }
    unsigned long long preVersion = GetLeValue(checkPointData->checkPointVersion);

    checkPointBlockId += GetLeValue(checkPointData->checkPointPackBlockCount) - 1;
    auto checkPoint2 = GetCheckpoint(checkPointBlockId);
    if (!checkPoint2) {
        return nullptr;
    }
    checkPointData = reinterpret_cast<struct CheckPointData *>(checkPoint2.get());
    unsigned long long curVersion = GetLeValue(checkPointData->checkPointVersion);
    if (curVersion == preVersion) {
        *version = curVersion;
        return checkPoint1;
    }
    return nullptr;
}

bool LoadCheckPoint::AllocateCheckpointData()
{
    SuperBlockData *superBlockData = superBlockInfo_->rawSuper;
    uint32_t checkPointPayload = GetLeValue(superBlockData->checkPointPayload);
    if (checkPointPayload > AlignUpCount(MAX_CP_PAYLOAD, HMFS_BLOCK_SIZE)) {
        return false;
    }
    unsigned long blockSize = superBlockInfo_->blockSize;
    uint32_t checkPointBlocks = 1 + checkPointPayload;
    superBlockInfo_->checkPointData = static_cast<CheckPointData *>(malloc(checkPointBlocks * blockSize));
    return superBlockInfo_->checkPointData != nullptr;
}

int32_t LoadCheckPoint::GetAvailCheckpoint()
{
    HMFS_DEBUG("Enter GetAvailCheckpoint");
    if (!AllocateCheckpointData()) {
        return -ENOMEM;
    }

    std::unique_ptr<uint8_t[]> currentCheckPoint = nullptr;
    unsigned long long version = 0;
    if (!DetermineCurrentCheckpoint(currentCheckPoint, version)) {
        free(superBlockInfo_->checkPointData);
        superBlockInfo_->checkPointData = nullptr;
        return -EINVAL;
    }

    HMFS_INFO("CKPT version = %llx", version);
    if (!CopyCheckpointData(currentCheckPoint.get())) {
        return EXIT_ERR_CODE;
    }

    return SUCCESS_CODE;
}

bool LoadCheckPoint::DetermineCurrentCheckpoint(std::unique_ptr<uint8_t[]> &currentCheckPoint,
    unsigned long long &version)
{
    unsigned long long checkPoint1Version = 0;
    unsigned long long checkPoint2Version = 0;
    unsigned long long checkPointBlockId = GetLeValue(superBlockInfo_->rawSuper->checkPointBlockId);
    auto checkPoint1 = ValidateCheckpoint(checkPointBlockId, &checkPoint1Version);
    checkPointBlockId += 1 << GetLeValue(superBlockInfo_->rawSuper->logBlksPerSeg);
    auto checkPoint2 = ValidateCheckpoint(checkPointBlockId, &checkPoint2Version);
    constexpr int32_t checkPointIdTwo = 2;
    if (checkPoint1 && checkPoint2) {
        if (checkPoint2Version > checkPoint1Version) {
            currentCheckPoint = std::move(checkPoint2);
            superBlockInfo_->currentCheckPointId = checkPointIdTwo;
            version = checkPoint2Version;
        } else {
            currentCheckPoint = std::move(checkPoint1);
            superBlockInfo_->currentCheckPointId = 1;
            version = checkPoint1Version;
        }
    } else if (checkPoint1) {
        currentCheckPoint = std::move(checkPoint1);
        superBlockInfo_->currentCheckPointId = 1;
        version = checkPoint1Version;
    } else if (checkPoint2) {
        currentCheckPoint = std::move(checkPoint2);
        superBlockInfo_->currentCheckPointId = checkPointIdTwo;
        version = checkPoint2Version;
    } else {
        return false;
    }
    return true;
}

bool LoadCheckPoint::CopyCheckpointData(uint8_t *currentCheckPoint)
{
    HMFS_CHECK_ONLY_EXPER(currentCheckPoint != nullptr, return false);
    unsigned long blockSize = superBlockInfo_->blockSize;
    int ret = memcpy_s(superBlockInfo_->checkPointData, blockSize, currentCheckPoint, blockSize);
    HMFS_CHECK(ret == 0, return false, "Failed to copy checkpoint");

    uint32_t checkPointBlocks = 1 + GetLeValue(superBlockInfo_->rawSuper->checkPointPayload);
    if (checkPointBlocks > 1) {
        unsigned long long blockNumber = GetLeValue(superBlockInfo_->rawSuper->checkPointBlockId);
        if (currentCheckPoint == reinterpret_cast<uint8_t *>(superBlockInfo_->checkPointData)) {
            blockNumber += 1 << GetLeValue(superBlockInfo_->rawSuper->logBlksPerSeg);
        }
        for (uint32_t i = 1; i < checkPointBlocks; i++) {
            unsigned char *checkPointData = reinterpret_cast<unsigned char *>(superBlockInfo_->checkPointData);
            ret = HmfsIo::GetInstance().DevReadBlock(currentCheckPoint, blockNumber + i);
            HMFS_ASSERT(ret >= 0);
            ret = memcpy_s(checkPointData + i * blockSize, blockSize, currentCheckPoint, blockSize);
            HMFS_CHECK(ret == 0, return false, "Failed to copy checkpoint");
        }
    }
    return true;
}

int32_t LoadCheckPoint::HmfsCheckCheckPoint()
{
    SuperBlockData *superBlockData = superBlockInfo_->rawSuper;
    CheckPointData *checkPointData = superBlockInfo_->checkPointData;
    uint32_t total = GetLeValue(superBlockData->segmentCount);
    uint32_t fsMeta = GetLeValue(superBlockData->segmentCountInCP) + GetLeValue(superBlockData->segmentCountInSIT);
    fsMeta += GetLeValue(superBlockData->segmentCountInNAT) + GetLeValue(checkPointData->reservedSegmentCount);
    fsMeta += GetLeValue(superBlockData->segmentCountInSSA);
    if (fsMeta >= total) {
        return EXIT_ERR_CODE;
    }

    if (ValidateLayout(superBlockData, checkPointData, fsMeta)) {
        return EXIT_ERR_CODE;
    }

    if (ValidateUserBlockCount(superBlockData, checkPointData)) {
        return EXIT_ERR_CODE;
    }

    if (ValidateSegmentOffsets(checkPointData, superBlockData)) {
        return EXIT_ERR_CODE;
    }

    if (ValidateBitmapSizes(checkPointData, superBlockData)) {
        return EXIT_ERR_CODE;
    }

    return ValidateCpStartSum(superBlockData, checkPointData);
}

bool LoadCheckPoint::ValidateLayout(SuperBlockData *superBlockData, CheckPointData *checkPointData, uint32_t fsMeta)
{
    HMFS_CHECK_ONLY_EXPER(superBlockData != nullptr && checkPointData != nullptr, return false);
    uint32_t overprovisionedSegments = GetLeValue(checkPointData->overprovisionSegmentCount);
    uint32_t reservedSegments = GetLeValue(checkPointData->reservedSegmentCount);
    if (!(GetLeValue(superBlockData->features) & NativeToLE32(HMFS_FEATURE_RO)) &&
        (fsMeta < HMFS_MIN_SEGMENT_COUNT || overprovisionedSegments == 0 || reservedSegments == 0)) {
        HMFS_ERROR("Wrong layout: check mkfs.hmfs version");
        return true;
    }
    return false;
}

bool LoadCheckPoint::ValidateUserBlockCount(SuperBlockData *superBlockData, CheckPointData *checkPointData)
{
    HMFS_CHECK_ONLY_EXPER(checkPointData != nullptr && superBlockData != nullptr, return false);
    uint32_t userBlockCount = GetLeValue(checkPointData->userBlockCount);
    uint32_t segmentCountMain = GetLeValue(superBlockData->segmentCountInMain) +
        (NativeToLE32(HMFS_FEATURE_RO) ? 1 : 0);
    uint32_t logBlocksPerSeg = GetLeValue(superBlockData->logBlksPerSeg);
    if (userBlockCount == 0 || userBlockCount >= segmentCountMain << logBlocksPerSeg) {
        HMFS_ERROR("Wrong userBlockCount(%u)", userBlockCount);
        if (load_->HmfsShouldProceed(superBlockData, GetLeValue(checkPointData->checkPointFlags)) != EXIT_ERR_CODE) {
            return true;
        }
        if (config_->fixOn == 0) {
            return true;
        }
        if (GetLeValue(checkPointData->checkPointFlags) & (CP_FLAG_FSCK | CP_FLAG_RESIZEFS)) {
            uint32_t segmentCountMain = GetLeValue(superBlockData->segmentCount) -
                (GetLeValue(superBlockData->segmentCountInCP) + GetLeValue(superBlockData->segmentCountInSIT) +
                GetLeValue(superBlockData->segmentCountInNAT) + GetLeValue(superBlockData->segmentCountInSSA));
            if (segmentCountMain != GetLeValue(superBlockData->segmentCountInMain)) {
                HMFS_ERROR("Inconsistent segment_cnt_main %u in superBlock", segmentCountMain << logBlocksPerSeg);
                return true;
            }
            uint32_t validUserBlocks = ((GetLeValue(superBlockData->segmentCountInMain) -
                GetLeValue(checkPointData->overprovisionSegmentCount)) * config_->segmentBlockCount);
            HMFS_INFO("Fix wrong userBlockCount in CP: (%u) -> (%u)", userBlockCount, validUserBlocks);
            SetLeValue(checkPointData->userBlockCount, validUserBlocks);
            config_->bugOn = 1;
        }
    }
    return false;
}

bool LoadCheckPoint::ValidateSegmentOffsets(CheckPointData *checkPointData, SuperBlockData *superBlockData)
{
    HMFS_CHECK_ONLY_EXPER(checkPointData != nullptr && superBlockData != nullptr, return false);
    uint32_t mainSegmentss = GetLeValue(superBlockData->segmentCountInMain);
    uint32_t blocksPerSeg = superBlockInfo_->blocksPerSeg;
    for (int32_t i = 0; i < CURSEG_NODE_TYPE_COUNT; i++) {
        if (GetLeValue(checkPointData->curNodeSegNo[i]) >= mainSegmentss ||
            GetLeValue(checkPointData->curNodeBlkOffset[i]) >= blocksPerSeg) {
            return true;
        }
    }
    for (int32_t i = 0; i < CURSEG_DATA_TYPE_COUNT; i++) {
        if (GetLeValue(checkPointData->curDataSegNo[i]) >= mainSegmentss ||
            GetLeValue(checkPointData->curDataBlkOffset[i]) >= blocksPerSeg) {
            return true;
        }
    }
    return false;
}

bool LoadCheckPoint::ValidateBitmapSizes(CheckPointData *checkPointData, SuperBlockData *superBlockData)
{
    HMFS_CHECK_ONLY_EXPER(checkPointData != nullptr && superBlockData != nullptr, return false);
    const uint32_t segmentDivisor = 2;
    const uint32_t bitsPerByte = 8;
    uint32_t sitSegments = GetLeValue(superBlockData->segmentCountInSIT);
    uint32_t natSegments = GetLeValue(superBlockData->segmentCountInNAT);
    uint32_t logBlocksPerSeg = GetLeValue(superBlockData->logBlksPerSeg);
    uint32_t sitBitmapSize = GetLeValue(checkPointData->sitVersionBitmapSize);
    uint32_t natBitmapSize = GetLeValue(checkPointData->natVersionBitmapSize);
    if (sitBitmapSize != ((sitSegments / segmentDivisor) << logBlocksPerSeg) / bitsPerByte ||
        natBitmapSize != ((natSegments / segmentDivisor) << logBlocksPerSeg) / bitsPerByte) {
        HMFS_ERROR("Wrong bitmap size: sit(%u), nat(%u)", sitBitmapSize, natBitmapSize);
        return true;
    }
    return false;
}

int32_t LoadCheckPoint::ValidateCpStartSum(SuperBlockData *superBlockData, CheckPointData *checkPointData)
{
    HMFS_CHECK_ONLY_EXPER(superBlockData != nullptr && checkPointData != nullptr, return EXIT_ERR_CODE);
    uint32_t checkPointStart = LE32ToNative(checkPointData->summaryStartBlock);
    uint32_t checkPointPayload = LE32ToNative(superBlockData->checkPointPayload);
    if (checkPointStart < checkPointPayload + 1 || checkPointStart >
        superBlockInfo_->blocksPerSeg - 1 - CURSEG_TYPE_MAX) {
        HMFS_ERROR("Wrong checkPointStart(%u) or checkPointPayload(%u)", checkPointStart, checkPointPayload);
        if ((GetLeValue(superBlockData->features) & HMFS_FEATURE_SB_CHKSUM)) {
            return EXIT_ERR_CODE;
        }
        SetLeValue(superBlockData->checkPointPayload, checkPointStart - 1);
        UpdateSuperBlock(superBlockData, SUPER_BLOCK_MASK_ALL);
    }
    return SUCCESS_CODE;
}

int32_t LoadCheckPoint::HmfsLoadCpData()
{
    HMFS_DEBUG("Enter HmfsLoadCpData");
    int32_t ret = GetAvailCheckpoint();
    if (ret != SUCCESS_CODE) {
        HMFS_ERROR("Failed to find valid checkpoint");
        PrintCheckpointInfo(superBlockInfo_->checkPointData, config_->debugLevel, config_->layout);
        return EXIT_ERR_CODE;
    }
    config_->bugOn = 0;
    if (HmfsCheckCheckPoint() != SUCCESS_CODE) {
        HMFS_ERROR("Failed to check checkpoint");
        PrintCheckpointInfo(superBlockInfo_->checkPointData, config_->debugLevel, config_->layout);
        return EXIT_ERR_CODE;
    }

    PrintCheckpointInfo(superBlockInfo_->checkPointData, config_->debugLevel, config_->layout);

    if (config_->func != CmdFunc::FSCK && config_->func != CmdFunc::DUMP &&
        !IsCheckpointFlags(superBlockInfo_->checkPointData, CP_FLAG_UMOUNT)) {
        HMFS_ERROR("Mount unclean image to replay log first");
        return EXIT_ERR_CODE;
    }

    return SUCCESS_CODE;
}
} // namespace Hmfs
} // namespace OHOS